{- This file is part of funbot.
 -
 - Written in 2015, 2016, 2017 by fr33domlover <fr33domlover@riseup.net>.
 -
 - ♡ Copying is an act of love. Please copy, reuse and share.
 -
 - The author(s) have dedicated all copyright and related and neighboring
 - rights to this software to the public domain worldwide. This software is
 - distributed without any warranty.
 -
 - You should have received a copy of the CC0 Public Domain Dedication along
 - with this software. If not, see
 - <http://creativecommons.org/publicdomain/zero/1.0/>.
 -}

-- For deriving trivial no-op Hashable instances for newtypes
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

-- To avoid making irc-fun-bot depend on persistent
{-# LANGUAGE StandaloneDeriving #-}

module FunBot.Types
    ( RepoName (..)
    , RepoSpace (..)
    , BranchName (..)
    , DevHostLabel (..)
    , DevHost (..)
    , FeedLabel (..)
    , ShortcutLabel (..)
    , LocationLabel (..)
    , LocationText (..)
    , Filter (..)
    , BranchFilter
    , RepoAnnSpec (..)
    , NewsItemFields (..)
    , NewsAnnSpec (..)
    , NewsFeed (..)
    , BotEnv (..)
    , LinkDetectionMode (..)
    , ChanSettings (..)
    , Settings (..)
    , ShortcutDeprecated (..)
    , SettingsOption
    , SettingsTree
    , HistoryDisplay (..)
    , UserOptions (..)
    , BotState (..)
    , BotSession
    , BotSessionDB
    , BotSessionDBS
    , ExtEventSource
    , ExtEventHandler
    , Respond
    , BotCmd
    )
where

import Control.Concurrent.Chan (Chan)
import Data.Aeson (FromJSON (..), ToJSON (..))
import Data.CaseInsensitive (CI)
import Data.Functor ((<$>))
import Data.Hashable (Hashable)
import Data.HashMap.Lazy (HashMap)
import Data.HashSet (HashSet)
import Data.Sequence (Seq)
import Data.Settings.Types (Section (..), Option (..))
import Data.Text (Text)
import Data.Time.Clock (UTCTime)
import Database.Persist.Class (PersistField)
import Database.Persist.Sql hiding (Filter)
import Network.HTTP.Client (Manager)
import Network.IRC.Fun.Types.Base
import Web.Feed.Collect (CommandQueue)

import qualified Data.CaseInsensitive as CI

import Database.Persist.Class.Local ()
import Database.Persist.Schema
import Network.IRC.Fun.Bot.Types (Session, EventSource, EventHandler, Command)

import FunBot.ExtEvents (ExtEvent)

instance (FromJSON s, CI.FoldCase s) => FromJSON (CI s) where
    parseJSON v = CI.mk <$> parseJSON v

instance ToJSON s => ToJSON (CI s) where
    toJSON = toJSON . CI.original

deriving instance PersistField Nickname
deriving instance PersistFieldSql Nickname

deriving instance PersistField Channel
deriving instance PersistFieldSql Channel

deriving instance PersistField MsgContent
deriving instance PersistFieldSql MsgContent

-- | A version control repository name
newtype RepoName = RepoName { unRepoName :: CI Text }
    deriving (Eq, Hashable)

-- | A repo hosting service repo namespace, e.g. user or team name
newtype RepoSpace = RepoSpace { unRepoSpace :: CI Text }
    deriving (Eq, Hashable)

-- | A version control repository branch name
newtype BranchName = BranchName { unBranchName :: Text }
    deriving (Eq, FromJSON, ToJSON)

-- | A repo hosting service host label
newtype DevHostLabel = DevHostLabel { unDevHostLabel :: Text }
    deriving (Eq, Hashable)

-- | A repo hosting service DNS name
newtype DevHost = DevHost { unDevHost :: CI Text } deriving (Eq, Hashable)

-- | TODO
newtype FeedLabel = FeedLabel { unFeedLabel :: CI Text }
    deriving (Eq, Hashable)

-- | TODO
newtype ShortcutLabel = MkShortcutLabel { unShortcutLabel :: CI Text }
    deriving (Eq, Hashable, PersistField, PersistFieldSql)

-- | TODO
newtype LocationLabel = LocationLabel { unLocationLabel :: CI Text }
    deriving (Eq, Hashable, PersistField, PersistFieldSql)

-- | TODO
newtype LocationText = LocationText { unLocation :: Text }
    deriving (PersistField, PersistFieldSql)

-- | Generic item filter
data Filter a = Accept [a] | Reject [a]

-- | Chooser for repo branches whose commits should be announced to IRC
type BranchFilter = Filter BranchName

-- | Configuration for announcing a git repo's events to a specific channel
data RepoAnnSpec = RepoAnnSpec
    { -- | IRC channel into which to announce
      rasChannel       :: Channel
      -- | Branch filter to choose which branches to announce
    , rasBranches      :: BranchFilter
      -- | Whether to report all commits in a push ('True') or shorten long
      -- pushes to avoid channel spam ('False').
    , rasAllCommits    :: Bool
      -- | Whether to announce commits and tags.
    , rasCommits       :: Bool
      -- | Whether to announce issues.
    , rasIssues        :: Bool
      -- | Whether to announce merge requests.
    , rasMergeRequests :: Bool
      -- | Whether to announce snippets.
    , rasSnippets      :: Bool
      -- | Whether to announce notes (comments).
    , rasNotes         :: Bool
      -- | Whether to announce recent fresh events.
    , rasNew           :: Bool
      -- | Whether to announce older events sent retroactively.
    , rasOld           :: Bool
      -- | Wherher to announce events whose time isn't specified.
    , rasUntimed       :: Bool
    }

-- | Pick news item fields to display
data NewsItemFields = NewsItemFields
    { dispFeedTitle :: Bool
    , dispAuthor    :: Bool
    , dispUrl       :: Bool
    }

-- | Configuration for announcing news items
data NewsAnnSpec = NewsAnnSpec
    { -- | IRC channels into which to announce
      nAnnChannels :: [Channel]
      -- | Filter for picking news item fields to display or hide
    , nAnnFields   :: NewsItemFields
    }

-- | A web news feed from which the bot can read and announce new items
data NewsFeed = NewsFeed
    { -- | The feed URL
      nfUrl     :: Text
      -- | Whether the feed watcher is watching this feed
    , nfActive  :: Bool
      -- | Item announcement details
    , nfAnnSpec :: NewsAnnSpec
    }

-- | Read-only custom bot environment
data BotEnv = BotEnv
    { -- | Port on which the web hook event source will run
      webHookSourcePort :: Int
      -- | An 'IO' action which schedules saving settings to disk. There is a
      -- wrapper in the 'Session' monad which uses this with the settings
      -- stored in bot state, so you probably don't need this field directly.
    , saveSettings      :: Settings -> IO ()
      -- | Similarly for user options.
    , saveUserOpts      :: HashMap Nickname UserOptions -> IO ()
      -- | Filename for logging feed listener errors
    , feedErrorLogFile  :: FilePath
      -- | Command queue for controlling the news feed watcher source
    , feedCmdQueue      :: CommandQueue
      -- | Ext event loopback queue for inserting ext events
    , loopbackQueue     :: Chan ExtEvent
      -- | SQL database connection pool
    , dbConnPool        :: ConnectionPool
      -- | HTTP client connection manager
    , httpManager       :: Manager
    }

data ShortcutDeprecated = ShortcutDeprecated
    { shPrefix   :: Text
    , shBefore   :: Text
    , shAfter    :: Text
    , shChannels :: [Channel]
    }

-- | How to detect web links in messages
data LinkDetectionMode = LDMMessage | LDMFirstWord | LDMAllWords

-- | Per-channel settings
data ChanSettings = ChanSettings
    { -- | Whether to display URL titles (the default is yes).
      csSayTitles  :: Maybe LinkDetectionMode
      -- | Whether to welcome new users when the channel is quiet.
    , csWelcome    :: Bool
      -- | Email address for async discussions.
    , csEmail      :: Text
      -- | Users who can ask the bot to send an arbitrary message in the
      -- channel. Can be useful but also dangerous, manage with care.
    , csPuppeteers :: HashSet Nickname
      -- | URL of an IRCBrowse instance for the specific channel.
    , csBrowse     :: Maybe Text
    }

-- | User-modifiable bot behavior settings
data Settings = Settings
    { -- | Maps a host label to Git repo space+name to annoucement details
      stGitAnnChans  :: HashMap
                            DevHostLabel
                            (HashMap (RepoSpace, RepoName) (Seq RepoAnnSpec))
      -- | Maps a feed label to its URL and announcement details
    , stWatchedFeeds :: HashMap FeedLabel NewsFeed
    , stShortcuts    :: HashMap ShortcutLabel ShortcutDeprecated
      -- | Per-channel settings
    , stChannels     :: HashMap Channel ChanSettings
      -- | Maps host names to host labels
    , stDevHosts     :: HashMap DevHost DevHostLabel
      -- | Users who can ask the bot to send an arbitrary message in an
      -- arbitrary channel. This gives a lot of power but is also dangerous,
      -- use with care. There are also per-channel puppeteers, see
      -- 'ChanSettings'.
    , stPuppeteers   :: HashSet Nickname
      -- | Nicknames of people trusted not to abuse the bot
    , stAngels       :: HashSet Nickname
      -- | Nicknames of people causing trouble and harming the bot
    , stDemons       :: HashSet Nickname
    }

-- | Alias for the settings option type
type SettingsOption = Option BotSession

-- | Alias for the settings section type
type SettingsTree = Section BotSession

-- | History display options per channel
data HistoryDisplay = HistoryDisplay
    { -- | Whether channel history should be displayed
      hdEnabled  :: Bool
      -- | Maximal number of messages to show
    , hdMaxLines :: Int
    }

-- | Per-user options, consider private user info
data UserOptions = UserOptions
    { -- | History display options per channel
      uoHistoryDisplay :: HashMap Channel HistoryDisplay
    }

-- | Read-write custom bot state
data BotState = BotState
    { -- | User-modifiable bot behavior settings
      bsSettings    :: Settings
      -- | Settings tree and access definition for UI
    , bsSTree       :: SettingsTree
      -- | Per-user options
    , bsUserOptions :: HashMap Nickname UserOptions
      -- | Time of last message per channel.
    , bsLastMsgTime :: HashMap Channel UTCTime
      -- | Channels for which puppet mode is enabled, and by which user.
    , bsPuppet      :: HashMap Channel Nickname
      -- | Whether private puppet mode is enabled, and by which user. It allows
      -- the user to ask the bot to send a private message to another user.
    , bsPrivPuppet  :: Maybe Nickname
      -- | Users who have connected to IRC have been notified about memos, and
      -- the memos will be sent to them when they speak. The 'Bool' says
      -- whether there are private memos. The set is a set of channels in
      -- which there are memos.
    , bsMemoReady   :: HashMap Nickname (Bool, HashSet Channel)
    }

-- | Shortcut alias for bot session monad
type BotSession = Session BotEnv BotState

-- | Monad for SQL queries
type BotSessionDB = SqlPersistT BotSession

-- | Monad for SQL queries with schema awareness
type BotSessionDBS = SchemaT SqlBackend BotSession

-- | Shortcut alias for event source function type
type ExtEventSource = EventSource BotEnv BotState ExtEvent

-- | Shortcut alias for event handler function type
type ExtEventHandler = EventHandler BotEnv BotState ExtEvent

-- | The type of command response functions
type Respond
    =  Maybe Channel
    -> Nickname
    -> [Text]
    -> (MsgContent -> BotSession ())
    -> BotSession ()

-- | Bot command type
type BotCmd = Command BotEnv BotState
